package com.ideaaedi.zoo.commonbase.context;


import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlCallable;
import com.alibaba.ttl.TtlRunnable;
import com.alibaba.ttl.threadpool.TtlExecutors;
import com.ideaaedi.zoo.commonbase.component.tenant.TenantScope;
import com.ideaaedi.zoo.commonbase.component.tenant.UserIdAndTenantScope;
import com.ideaaedi.zoo.commonbase.constant.ZooConstant;
import com.ideaaedi.zoo.commonbase.entity.codemsg.ZooBaseCodeMsgEnum;
import com.ideaaedi.zoo.commonbase.exception.ZooException;
import com.ideaaedi.zoo.commonbase.util.IpUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutorService;

/**
 * 默认的zoo context实现提供器
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 1.0.0
 */
@Slf4j
public class DefaultZcProvider implements ZcProvider {
    
    /**
     * <p>
     * 项目使用{@link TransmittableThreadLocal}作为{@link ThreadLocal}的实现，因此使用异步线程时，请务必搭配以下任一方式进行使用
     *     <ul>
     *         <li>方式1：使用{@link TtlRunnable}或{@link TtlCallable}包装{@link Runnable}</li>
     *         <li>方式2：使用{@link TtlExecutors}的相关方法包装已有线程池，如：使用{@link TtlExecutors#getTtlExecutorService}包装
     *         {@link ExecutorService}</li>
     *     </ul>
     * </p>
     * <p>
     *     解释1：{@link TransmittableThreadLocal}仅在新线程创建时复制父线程的{@link ThreadLocal}值。如果是复用线程池中已有的线程的话，
     *           光{@link TransmittableThreadLocal}是获取不到提交异步任务时业务线程的{@link ThreadLocal}值的，因此还必须搭配上述两种
     *           方式之一来进行使用，确保能正常获取到提交异步任务时的业务线程的{@link ThreadLocal}值
     * </p>
     * <p>
     *    解释2：{@link TtlRunnable}（{@link TtlCallable}）会自动管理{@link TransmittableThreadLocal}，无用手动加载或清理
     *    <ul>
     *        <li>在异步任务开始执行前：{@link TtlRunnable}（{@link TtlCallable}）会保存当前线程中所有{@link TransmittableThreadLocal}
     *        变量的副本</li>
     *        <li>在异步任务执行过程中：这些{@link TransmittableThreadLocal}变量可以被修改或使用，而这些更改的影响范围仅限该任务</li>
     *        <li>在异步任务执行完毕后：{@link TtlRunnable}（{@link TtlCallable}）会自动恢复{@link TransmittableThreadLocal}
     *        到任务执行前的状态，从而确保这个线程在处理下一个任务时不会受到之前任务的影响</li>
     *    </ul>
     * </p>
     *
     * <hr />
     * 更多说明可参考<a href="https://gitee.com/JustryDeng/demo/tree/ttl/basic/src/main/java/com/ideaaedi/demo">here</a>
     */
    protected static final ThreadLocal<Map<String, Object>> contextThreadLocal = new TransmittableThreadLocal<>() {
        
        @Override
        protected Map<String, Object> initialValue() {
            return new HashMap<>(16);
        }
        
        /**
         * 重写此方法，避免TTL线程和父线程使用同一个TTL值对象，保证并发安全
         */
        @Override
        public Map<String, Object> copy(Map<String, Object> parentValue) {
            return doCopy(parentValue);
        }
        
        /**
         * 重写此方法，避免普通线程和父线程使用同一个TTL对象
         * <pre>
         * 注：使用{@link TransmittableThreadLocal}后，,虽然我们能在业务测主动保证：如果要
         *    使用新线程的话，都使用TTL线程；但是一些第三方框架里面的东西，我们没法保证，所以这里
         *    重写此方法，使第三方框架创建普通线程时，不直接复用父线程的TTL值对象，保证并发安全。
         *
         * 举一个不重写此方法，导致的并发问题案例：
         * 1. spring-boot启动时，在main线程中初始化了某个TTL，initialValue返回了一个objectA对象
         * 2. 随着spring-boot启动的推进，tomcat创建WEB线程池，并创建了一些普通的线程放进线程池中
         *    注：因为其是第三方框架不是我们的业务代码，所以没法让他创建TTL线程
         * 3. 步骤2中创建的线程获取到的TTL值，是通过{@link TransmittableThreadLocal#childValue(Object)}
         *    方法获取的，看源码可知，该TTL值即是父线程的TTL值对象，是同一个TTL对象，即objectA对象
         * 4. 也就是说：tomcat线程池中一堆线程，各自的TTL值对象都是objectA对象，那么当并发时，可能就会存在并发问题
         * </pre>
         */
        @Override
        protected Map<String, Object> childValue(Map<String, Object> parentValue) {
            return doCopy(parentValue);
        }
        
        /**
         * (non-javadoc)
         */
        private Map<String, Object> doCopy(Map<String, Object> parentValue) {
            if (parentValue == null) {
                return new HashMap<>(1);
            }
            return new HashMap<>(parentValue);
        }
    };
    
    /**
     * 用于操作上下文的内部对象
     */
    protected static final ZcProvider.Inner INNER = new DefaultZcProvider.Inner();
    
    @Nonnull
    @Override
    public Map<String, Object> unmodifiableContext() {
        return Collections.unmodifiableMap(contextThreadLocal.get());
    }
    
    @Override
    public void clear() {
        contextThreadLocal.get().clear();
        contextThreadLocal.remove();
    }
    
    @Override
    public ZcProvider.Inner provideInner() {
        return INNER;
    }
    
    @Override
    public ZcProvider.Http provideHttp() {
        return new DefaultZcProvider.Http();
    }
    
    @Override
    public ZcProvider.Auth provideAuth(ZcProvider.Inner inner) {
        return new DefaultZcProvider.Auth(inner);
    }
    
    @Override
    public ZcProvider.TenantCt provideTenantCt(ZcProvider.Inner inner) {
        return new DefaultZcProvider.TenantCt(inner);
    }
    
    @Override
    public ZcProvider.AuditLog provideAuditLog(ZcProvider.Inner inner) {
        return new DefaultZcProvider.AuditLog(inner);
    }
    
    @Override
    public ZcProvider.Complex provideComplex(ZcProvider.Auth auth, ZcProvider.TenantCt tenantCt) {
        return new DefaultZcProvider.Complex(auth, tenantCt);
    }
    
    /**
     * 内部基础能力，不和任何组件相关
     */
    protected static class Inner implements ZcProvider.Inner {
        
        @Override
        public void put(String key, Object value) {
            contextThreadLocal.get().put(key, value);
        }
        
        @Nullable
        @Override
        public <T> T get(String key) {
            //noinspection unchecked
            return (T) contextThreadLocal.get().get(key);
        }
        
        @Override
        public void remove(String key, String... moreKeys) {
            Map<String, Object> context = contextThreadLocal.get();
            if (key != null) {
                context.remove(key);
            }
            if (moreKeys != null) {
                for (String moreKey : moreKeys) {
                    context.remove(moreKey);
                }
            }
        }
        
        @Nonnull
        @Override
        public Map<String, Object> context() {
            return contextThreadLocal.get();
        }
        
        @Override
        public void clear() {
            contextThreadLocal.get().clear();
        }
    }
    
    /**
     * 对其他能力进行组装而来的复合能力
     */
    protected static class Complex implements ZcProvider.Complex {
        
        /**
         * 认证鉴权
         */
        protected final ZcProvider.Auth auth;
        
        /**
         * 租户数据域
         */
        protected final ZcProvider.TenantCt tenantCt;
        
        public Complex(ZcProvider.Auth auth, ZcProvider.TenantCt tenantCt) {
            this.auth = auth;
            this.tenantCt = tenantCt;
            Objects.requireNonNull(auth, "auth cannot be null.");
            Objects.requireNonNull(tenantCt, "tenantCt cannot be null.");
        }
        
        @Override
        public <T extends UserIdAndTenantScope> void setUserIdAndTenantScope(@Nonnull T specUserTenant) {
            setUserIdAndTenantScope(specUserTenant.userId(), specUserTenant);
        }
        
        @Override
        public void setUserIdAndTenantScope(@Nonnull Long userId, @Nullable TenantScope tenantScope) {
            Objects.requireNonNull(userId);
            auth.setUserId(userId);
            if (tenantScope != null) {
                tenantCt.setTenantScope(tenantScope);
            }
        }
        
        @Override
        @Nonnull
        public Pair<Long, TenantScope> clearUserInfo() {
            // 获取
            Long userId = auth.currUserId();
            TenantScope tenantScope = tenantCt.currTenantScope();
            // 移除
            auth.clear();
            tenantCt.clear();
            return Pair.of(userId, tenantScope);
        }
        
        @Override
        public void clear(@Nullable UserIdAndTenantScope specUserTenant) {
            if (specUserTenant == null) {
                return;
            }
            Long existUserId = auth.currUserId();
            long userId = specUserTenant.userId();
            if ((Long.valueOf(userId)).equals(existUserId)) {
                auth.clear();
                tenantCt.clear();
            } else {
                log.warn("ignore clear, userId not match. existUserId -> {}, userId -> {}.", existUserId, userId);
            }
        }
    }
    
    /**
     * 租户相关
     */
    protected static class TenantCt implements ZcProvider.TenantCt {
        
        protected final ZcProvider.Inner inner;
        
        public TenantCt(ZcProvider.Inner inner) {
            this.inner = inner;
        }
        
        @Override
        public void setTenantScope(@Nullable TenantScope tenantScope) {
            if (tenantScope == null) {
                return;
            }
            inner.put(ZooConstant.CURR_TENANT_SCOPE, tenantScope);
        }
        
        @Nullable
        @Override
        public TenantScope currTenantScope() {
            return inner.get(ZooConstant.CURR_TENANT_SCOPE);
        }
        
        @Nonnull
        @Override
        public TenantScope currTenantScopeNonNull() {
            final TenantScope tenantScope = currTenantScope();
            if (tenantScope == null) {
                throw new ZooException(ZooBaseCodeMsgEnum.OBTAIN_USER_TENANT_SCOPE_FAIL);
            }
            return tenantScope;
        }
        
        @Nullable
        @Override
        public String currInsertTenant() {
            TenantScope tenantScope = currTenantScope();
            if (tenantScope == null) {
                return null;
            }
            return tenantScope.insertTenant();
        }
        
        @Nonnull
        @Override
        public String currInsertTenantNonNull() {
            String insertTenant = currInsertTenant();
            if (StringUtils.isBlank(insertTenant)) {
                throw new ZooException(ZooBaseCodeMsgEnum.OBTAIN_USER_TENANT_INSERT_SCOPE_FAIL);
            }
            return insertTenant;
        }
        
        @Nullable
        @Override
        public Collection<String> currReadableTenants() {
            TenantScope tenantScope = currTenantScope();
            if (tenantScope == null) {
                return null;
            }
            return tenantScope.readableTenants();
        }
        
        @Nullable
        @Override
        public Collection<String> currModifiableTenants() {
            TenantScope tenantScope = currTenantScope();
            if (tenantScope == null) {
                return null;
            }
            return tenantScope.modifiableTenants();
        }
        
        @Override
        public void clear() {
            inner.remove(ZooConstant.CURR_TENANT_SCOPE);
        }
    }
    
    /**
     * 认证鉴权相关
     */
    protected static class Auth implements ZcProvider.Auth {
        
        protected final ZcProvider.Inner inner;
        
        public Auth(ZcProvider.Inner inner) {
            this.inner = inner;
        }
        
        @Override
        public void setUserId(@Nullable Long userId) {
            if (userId == null) {
                return;
            }
            inner.put(ZooConstant.CURR_USER_ID, userId);
        }
        
        @Nullable
        @Override
        public Long currUserId() {
            return inner.get(ZooConstant.CURR_USER_ID);
        }
        
        @Nullable
        @Override
        public Long currUserIdDefault(Long defaultValue) {
            final Long currUserId = currUserId();
            if (currUserId == null) {
                return defaultValue;
            }
            return currUserId;
        }
        
        @Nonnull
        @Override
        public Long currUserIdNonNull() {
            final Long currUserId = currUserId();
            if (currUserId == null) {
                throw new ZooException(ZooBaseCodeMsgEnum.OBTAIN_USER_ID_FAIL);
            }
            return currUserId;
        }
        
        @Override
        public void clear() {
            inner.remove(ZooConstant.CURR_USER_ID);
        }
    }
    
    /**
     * 审计日志相关
     */
    protected static class AuditLog implements ZcProvider.AuditLog {
        
        protected final ZcProvider.Inner inner;
        
        public AuditLog(ZcProvider.Inner inner) {
            this.inner = inner;
        }
        
        @Override
        public void setLatestAuditLogId(Long id) {
            inner.put(ZooConstant.LATEST_AUDIT_LOG_ID, id);
        }
        
        @Override
        @Nullable
        public Long latestAuditLogId() {
            return (Long) inner.context().get(ZooConstant.LATEST_AUDIT_LOG_ID);
        }
    }
    
    /**
     * http相关
     */
    protected static class Http implements ZcProvider.Http {
        
        @Override
        @Nullable
        public HttpServletRequest httpServletRequest() {
            final RequestAttributes requestAttributes;
            try {
                requestAttributes = RequestContextHolder.currentRequestAttributes();
            } catch (IllegalStateException e) {
                log.debug(e.getMessage());
                return null;
            }
            if (!(requestAttributes instanceof ServletRequestAttributes)) {
                return null;
            }
            return ((ServletRequestAttributes) requestAttributes).getRequest();
        }
        
        @Override
        public HttpServletResponse httpServletResponse() {
            final RequestAttributes requestAttributes;
            try {
                requestAttributes = RequestContextHolder.currentRequestAttributes();
            } catch (IllegalStateException e) {
                return null;
            }
            if (!(requestAttributes instanceof ServletRequestAttributes)) {
                return null;
            }
            return ((ServletRequestAttributes) requestAttributes).getResponse();
        }
        
        @Override
        public void setAttribute(String key, Object value) {
            HttpServletRequest httpServletRequest = httpServletRequest();
            Objects.requireNonNull(httpServletRequest, "httpServletRequest should not be null.");
            httpServletRequest.setAttribute(key, value);
        }
        
        @Override
        public <T> T getAttribute(String key) {
            HttpServletRequest httpServletRequest = httpServletRequest();
            if (httpServletRequest == null) {
                return null;
            }
            //noinspection unchecked
            return (T) httpServletRequest.getAttribute(key);
        }
        
        @Nullable
        @Override
        public String clientIpAddress() {
            return clientIpAddress(httpServletRequest());
        }
        
        @Nullable
        @Override
        public String clientIpAddress(@Nullable HttpServletRequest httpServletRequest) {
            if (httpServletRequest == null) {
                return null;
            }
            return IpUtil.determineClientIpAddress(httpServletRequest);
        }
    }
}